package cn.yiya.shiji.views;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.PointF;
import android.graphics.RadialGradient;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AnimationUtils;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;

import cn.yiya.shiji.R;
import cn.yiya.shiji.utils.ColorUtil;
import cn.yiya.shiji.utils.ThemeUtil;
import cn.yiya.shiji.utils.ViewUtil;

/**
 * Created by chenjian on 2016/6/12.
 */
public class ToolbarRippleDrawable extends Drawable implements Animatable {

    private boolean mRunning = false;

    private Paint mShaderPaint;
    private Paint mFillPaint;
    private RadialGradient mInShader;
    private RadialGradient mOutShader;
    private Matrix mMatrix;
    private int mAlpha = 255;

    private RectF mBackgroundBounds;
    private Path mBackground;
    private int mBackgroundAnimDuration;
    private int mBackgroundColor;
    private float mBackgroundAlphaPercent;

    private PointF mRipplePoint;
    private float mRippleRadius;
    private int mRippleType;
    private int mMaxRippleRadius;
    private int mRippleAnimDuration;
    private int mRippleColor;
    private float mRippleAlphaPercent;
    private int mDelayClickType;

    private Interpolator mInInterpolator;
    private Interpolator mOutInterpolator;

    private long mStartTime;

    private boolean mPressed = false;

    private int mState = STATE_OUT;

    private static final int STATE_OUT = 0;
    private static final int STATE_PRESS = 1;
    private static final int STATE_HOVER = 2;
    private static final int STATE_RELEASE_ON_HOLD = 3;
    private static final int STATE_RELEASE = 4;

    private static final int TYPE_TOUCH_MATCH_VIEW = -1;
    private static final int TYPE_TOUCH = 0;
    private static final int TYPE_WAVE = 1;

    private static final float[] GRADIENT_STOPS = new float[]{0f, 0.99f, 1f};
    private static final float GRADIENT_RADIUS = 16;

    private ToolbarRippleDrawable(int backgroundAnimDuration, int backgroundColor, int rippleType, int delayClickType, int maxTouchRadius, int touchAnimDuration, int touchColor, Interpolator inInterpolator, Interpolator outInterpolator){
        mBackgroundAnimDuration = backgroundAnimDuration;
        mBackgroundColor = backgroundColor;

        mRippleType = rippleType;
        mMaxRippleRadius = maxTouchRadius;
        mRippleAnimDuration = touchAnimDuration;
        mRippleColor = touchColor;
        mDelayClickType = delayClickType;

        if(mRippleType == TYPE_TOUCH && mMaxRippleRadius <= 0)
            mRippleType = TYPE_TOUCH_MATCH_VIEW;

        mInInterpolator = inInterpolator;
        mOutInterpolator = outInterpolator;

        mFillPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mFillPaint.setStyle(Paint.Style.FILL);

        mShaderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mShaderPaint.setStyle(Paint.Style.FILL);

        mBackground = new Path();
        mBackgroundBounds = new RectF();

        mRipplePoint = new PointF();

        mMatrix = new Matrix();

        mInShader = new RadialGradient(0, 0, GRADIENT_RADIUS, new int[]{mRippleColor, mRippleColor, 0}, GRADIENT_STOPS, Shader.TileMode.CLAMP);
        if(mRippleType == TYPE_WAVE)
            mOutShader = new RadialGradient(0, 0, GRADIENT_RADIUS, new int[]{0, ColorUtil.getColor(mRippleColor, 0f), mRippleColor}, GRADIENT_STOPS, Shader.TileMode.CLAMP);
    }

    public int getDelayClickType(){
        return mDelayClickType;
    }

    public void setDelayClickType(int type){
        mDelayClickType = type;
    }

    @Override
    public void setAlpha(int alpha) {
        mAlpha = alpha;
    }

    @Override
    public void setColorFilter(ColorFilter filter) {
        mFillPaint.setColorFilter(filter);
        mShaderPaint.setColorFilter(filter);
    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }

    public long getClickDelayTime(){
        switch (mDelayClickType){
            case RippleDrawable.DELAY_CLICK_NONE:
                return -1;
            case RippleDrawable.DELAY_CLICK_UNTIL_RELEASE:
                if(mState == STATE_RELEASE_ON_HOLD)
                    return Math.max(mBackgroundAnimDuration, mRippleAnimDuration) - (SystemClock.uptimeMillis() - mStartTime);
                break;
            case RippleDrawable.DELAY_CLICK_AFTER_RELEASE:
                if(mState == STATE_RELEASE_ON_HOLD)
                    return 2 * Math.max(mBackgroundAnimDuration, mRippleAnimDuration) - (SystemClock.uptimeMillis() - mStartTime);
                else if(mState == STATE_RELEASE)
                    return Math.max(mBackgroundAnimDuration, mRippleAnimDuration) - (SystemClock.uptimeMillis() - mStartTime);
                break;
        }

        return -1;
    }

    private void setRippleState(int state){
        if(mState != state){
            mState = state;

            if(mState != STATE_OUT){
                if(mState != STATE_HOVER)
                    start();
                else
                    stop();
            }
            else
                stop();
        }
    }

    private boolean setRippleEffect(float x, float y, float radius){
        if(mRipplePoint.x != x || mRipplePoint.y != y || mRippleRadius != radius){
            mRipplePoint.set(x, y);
            mRippleRadius = radius;
            radius = mRippleRadius / GRADIENT_RADIUS;
            mMatrix.reset();
            mMatrix.postTranslate(x, y);
            mMatrix.postScale(radius, radius, x, y);
            mInShader.setLocalMatrix(mMatrix);
            if(mOutShader != null)
                mOutShader.setLocalMatrix(mMatrix);

            return true;
        }

        return false;
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        mBackgroundBounds.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
        mBackground.reset();
        mBackground.addRect(mBackgroundBounds, Path.Direction.CW);
    }

    @Override
    public boolean isStateful() {
        return true;
    }

    @Override
    protected boolean onStateChange(int[] state) {
        boolean pressed = ViewUtil.hasState(state, android.R.attr.state_pressed);

        if(mPressed != pressed){
            mPressed = pressed;

            if(mPressed){
                Rect bounds = getBounds();

                if(mState == STATE_OUT || mState == STATE_RELEASE){
                    if(mRippleType == TYPE_WAVE || mRippleType == TYPE_TOUCH_MATCH_VIEW)
                        mMaxRippleRadius = getMaxRippleRadius(bounds.exactCenterX(), bounds.exactCenterY());

                    setRippleEffect(bounds.exactCenterX(), bounds.exactCenterY(), 0);
                    setRippleState(STATE_PRESS);
                }
                else if(mRippleType == TYPE_TOUCH)
                    setRippleEffect(bounds.exactCenterX(), bounds.exactCenterY(), mRippleRadius);
            }
            else{
                if(mState != STATE_OUT){
                    if(mState == STATE_HOVER){
                        if(mRippleType == TYPE_WAVE|| mRippleType == TYPE_TOUCH_MATCH_VIEW)
                            setRippleEffect(mRipplePoint.x, mRipplePoint.y, 0);

                        setRippleState(STATE_RELEASE);
                    }
                    else
                        setRippleState(STATE_RELEASE_ON_HOLD);
                }
            }

            return true;
        }

        return false;
    }

    @Override
    public void draw(Canvas canvas) {
        switch (mRippleType) {
            case TYPE_TOUCH:
            case TYPE_TOUCH_MATCH_VIEW:
                drawTouch(canvas);
                break;
            case TYPE_WAVE:
                drawWave(canvas);
                break;
        }
    }

    private void drawTouch(Canvas canvas){
        if(mState != STATE_OUT){
            if(mBackgroundAlphaPercent > 0){
                mFillPaint.setColor(mBackgroundColor);
                mFillPaint.setAlpha(Math.round(mAlpha * mBackgroundAlphaPercent));
                canvas.drawPath(mBackground, mFillPaint);
            }

            if(mRippleRadius > 0 && mRippleAlphaPercent > 0){
                mShaderPaint.setAlpha(Math.round(mAlpha * mRippleAlphaPercent));
                mShaderPaint.setShader(mInShader);
                canvas.drawPath(mBackground, mShaderPaint);
            }
        }
    }

    private void drawWave(Canvas canvas){
        if(mState != STATE_OUT){
            if(mState == STATE_RELEASE){
                if(mRippleRadius == 0){
                    mFillPaint.setColor(mRippleColor);
                    canvas.drawPath(mBackground, mFillPaint);
                }
                else{
                    mShaderPaint.setShader(mOutShader);
                    canvas.drawPath(mBackground, mShaderPaint);
                }
            }
            else if(mRippleRadius > 0){
                mShaderPaint.setShader(mInShader);
                canvas.drawPath(mBackground, mShaderPaint);
            }
        }
    }

    private int getMaxRippleRadius(float x, float y){
        float x1 = x < mBackgroundBounds.centerX() ? mBackgroundBounds.right : mBackgroundBounds.left;
        float y1 = y < mBackgroundBounds.centerY() ? mBackgroundBounds.bottom : mBackgroundBounds.top;

        return (int) Math.round(Math.sqrt(Math.pow(x1 - x, 2) + Math.pow(y1 - y, 2)));
    }

    //Animation: based on http://cyrilmottier.com/2012/11/27/actionbar-on-the-move/

    public void cancel(){
        setRippleState(STATE_OUT);
    }

    private void resetAnimation(){
        mStartTime = SystemClock.uptimeMillis();
    }

    @Override
    public void start() {
        if(isRunning())
            return;

        resetAnimation();

        scheduleSelf(mUpdater, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION);
        invalidateSelf();
    }

    @Override
    public void stop() {
        if(!isRunning())
            return;

        mRunning = false;
        unscheduleSelf(mUpdater);
        invalidateSelf();
    }

    @Override
    public boolean isRunning() {
        return mRunning;
    }

    @Override
    public void scheduleSelf(Runnable what, long when) {
        mRunning = true;
        super.scheduleSelf(what, when);
    }

    private final Runnable mUpdater = new Runnable() {

        @Override
        public void run() {
            switch (mRippleType) {
                case TYPE_TOUCH:
                case TYPE_TOUCH_MATCH_VIEW:
                    updateTouch();
                    break;
                case TYPE_WAVE:
                    updateWave();
                    break;
            }

        }

    };

    private void updateTouch(){
        if(mState != STATE_RELEASE){
            float backgroundProgress = Math.min(1f, (float)(SystemClock.uptimeMillis() - mStartTime) / mBackgroundAnimDuration);
            mBackgroundAlphaPercent = mInInterpolator.getInterpolation(backgroundProgress) * Color.alpha(mBackgroundColor) / 255f;

            float touchProgress = Math.min(1f, (float)(SystemClock.uptimeMillis() - mStartTime) / mRippleAnimDuration);
            mRippleAlphaPercent = mInInterpolator.getInterpolation(touchProgress);

            setRippleEffect(mRipplePoint.x, mRipplePoint.y, mMaxRippleRadius * mInInterpolator.getInterpolation(touchProgress));

            if(backgroundProgress == 1f && touchProgress == 1f){
                mStartTime = SystemClock.uptimeMillis();
                setRippleState(mState == STATE_PRESS ? STATE_HOVER : STATE_RELEASE);
            }

        }
        else{
            float backgroundProgress = Math.min(1f, (float)(SystemClock.uptimeMillis() - mStartTime) / mBackgroundAnimDuration);
            mBackgroundAlphaPercent = (1f - mOutInterpolator.getInterpolation(backgroundProgress)) * Color.alpha(mBackgroundColor) / 255f;

            float touchProgress = Math.min(1f, (float)(SystemClock.uptimeMillis() - mStartTime) / mRippleAnimDuration);
            mRippleAlphaPercent = 1f - mOutInterpolator.getInterpolation(touchProgress);

            setRippleEffect(mRipplePoint.x, mRipplePoint.y, mMaxRippleRadius * (1f + 0.5f * mOutInterpolator.getInterpolation(touchProgress)));

            if(backgroundProgress == 1f && touchProgress == 1f)
                setRippleState(STATE_OUT);
        }

        if(isRunning())
            scheduleSelf(mUpdater, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION);

        invalidateSelf();
    }

    private void updateWave(){
        float progress = Math.min(1f, (float)(SystemClock.uptimeMillis() - mStartTime) / mRippleAnimDuration);

        if(mState != STATE_RELEASE){
            setRippleEffect(mRipplePoint.x, mRipplePoint.y, mMaxRippleRadius * mInInterpolator.getInterpolation(progress));

            if(progress == 1f){
                mStartTime = SystemClock.uptimeMillis();
                if(mState == STATE_PRESS)
                    setRippleState(STATE_HOVER);
                else{
                    setRippleEffect(mRipplePoint.x, mRipplePoint.y, 0);
                    setRippleState(STATE_RELEASE);
                }
            }
        }
        else{
            setRippleEffect(mRipplePoint.x, mRipplePoint.y, mMaxRippleRadius * mOutInterpolator.getInterpolation(progress));

            if(progress == 1f)
                setRippleState(STATE_OUT);
        }

        if(isRunning())
            scheduleSelf(mUpdater, SystemClock.uptimeMillis() + ViewUtil.FRAME_DURATION);

        invalidateSelf();
    }

    public static class Builder{
        private int mBackgroundAnimDuration = 200;
        private int mBackgroundColor;

        private int mRippleType;
        private int mMaxRippleRadius;
        private int mRippleAnimDuration = 400;
        private int mRippleColor;
        private int mDelayClickType;

        private Interpolator mInInterpolator;
        private Interpolator mOutInterpolator;

        public Builder(){}

        public Builder(Context context, int defStyleRes){
            this(context, null, 0, defStyleRes);
        }

        public Builder(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes){
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RippleDrawable, defStyleAttr, defStyleRes);
            int resId;

            backgroundColor(a.getColor(R.styleable.RippleDrawable_rd_backgroundColor, 0));
            backgroundAnimDuration(a.getInteger(R.styleable.RippleDrawable_rd_backgroundAnimDuration, context.getResources().getInteger(android.R.integer.config_mediumAnimTime)));
            rippleType(a.getInteger(R.styleable.RippleDrawable_rd_rippleType, ToolbarRippleDrawable.TYPE_TOUCH));
            delayClickType(a.getInteger(R.styleable.RippleDrawable_rd_delayClick, RippleDrawable.DELAY_CLICK_NONE));
            int type = ThemeUtil.getType(a, R.styleable.RippleDrawable_rd_maxRippleRadius);
            if(type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT)
                maxRippleRadius(a.getInteger(R.styleable.RippleDrawable_rd_maxRippleRadius, -1));
            else
                maxRippleRadius(a.getDimensionPixelSize(R.styleable.RippleDrawable_rd_maxRippleRadius, ThemeUtil.dpToPx(context, 48)));
            rippleColor(a.getColor(R.styleable.RippleDrawable_rd_rippleColor, ThemeUtil.colorControlHighlight(context, 0)));
            rippleAnimDuration(a.getInteger(R.styleable.RippleDrawable_rd_rippleAnimDuration, context.getResources().getInteger(android.R.integer.config_mediumAnimTime)));
            if((resId = a.getResourceId(R.styleable.RippleDrawable_rd_inInterpolator, 0)) != 0)
                inInterpolator(AnimationUtils.loadInterpolator(context, resId));
            if((resId = a.getResourceId(R.styleable.RippleDrawable_rd_outInterpolator, 0)) != 0)
                outInterpolator(AnimationUtils.loadInterpolator(context, resId));

            a.recycle();
        }

        public ToolbarRippleDrawable build(){
            if(mInInterpolator == null)
                mInInterpolator = new AccelerateInterpolator();

            if(mOutInterpolator == null)
                mOutInterpolator = new DecelerateInterpolator();

            return new ToolbarRippleDrawable(mBackgroundAnimDuration, mBackgroundColor, mRippleType, mDelayClickType, mMaxRippleRadius, mRippleAnimDuration, mRippleColor, mInInterpolator, mOutInterpolator);
        }

        public Builder backgroundAnimDuration(int duration){
            mBackgroundAnimDuration = duration;
            return this;
        }

        public Builder backgroundColor(int color){
            mBackgroundColor = color;
            return this;
        }

        public Builder rippleType(int type){
            mRippleType = type;
            return this;
        }

        public Builder delayClickType(int type){
            mDelayClickType = type;
            return this;
        }

        public Builder maxRippleRadius(int radius){
            mMaxRippleRadius = radius;
            return this;
        }

        public Builder rippleAnimDuration(int duration){
            mRippleAnimDuration = duration;
            return this;
        }

        public Builder rippleColor(int color){
            mRippleColor = color;
            return this;
        }

        public Builder inInterpolator(Interpolator interpolator){
            mInInterpolator = interpolator;
            return this;
        }

        public Builder outInterpolator(Interpolator interpolator){
            mOutInterpolator = interpolator;
            return this;
        }

    }
}
